﻿// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED!
// YOU MAY USE THIS CODE: HOWEVER THIS GRANTS NO FUTURE RIGHTS.
// see https://spsearchbestbetsmigration.codeplex.com/license and https://spsearchbestbetsmigration.codeplex.com/ for further details and license information

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.XPath;

using Microsoft.Office.Server.Search.Administration;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace SPSearchBestBetsMigration
{
    internal class SearchMigration
    {
        public enum MigScope
        {
            Farm,
            WebApp,
            SiteColl
        };

		public static void CreateElement(string elementName, string elementValue, XmlElement parentElement, XmlDocument xmlDoc)
		{
			XmlElement xmlElement = xmlDoc.CreateElement(elementName);
			XmlText xmlText = xmlDoc.CreateTextNode("");
			xmlText.Value = elementValue;
			parentElement.AppendChild(xmlElement);
			xmlElement.AppendChild(xmlText);
		}

        /// <summary>
        /// Export Search Keywords and Best Bets for the Farm
        /// </summary>
        /// <param name="outputPath">Out file path to be created or overwritten</param>
        public static void ExportSearchBestBets(string outputPath)
        {
            ExportSearchBestBets(MigScope.Farm, outputPath, null);
        }

        /// <summary>
        /// Export Search Keywords and Best Bets for the Farm
        /// </summary>
        /// <param name="scope">Scope to Export</param>
        /// <param name="outputPath">Out file path to be created or overwritten</param>
        /// <param name="spUrl">SharePoint Url</param>
        public static void ExportSearchBestBets(MigScope scope, string outputPath, string spUrl)
        {
    		try
			{
                XmlDocument xmlDoc = new XmlDocument();
                XmlTextWriter xmlTextWriter = new XmlTextWriter(outputPath, Encoding.UTF8);
                xmlTextWriter.Formatting = Formatting.Indented;
                xmlTextWriter.WriteProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
                xmlTextWriter.WriteStartElement("Sites");
                xmlTextWriter.Close();
                xmlDoc.Load(outputPath);

                switch (scope)
                {
                    case MigScope.Farm:

                        foreach (SPWebApplication webApp in SPWebService.ContentService.WebApplications)
                        {
                            foreach (SPSite site in webApp.Sites)
                            {
                                GetSiteKeywords(ref xmlDoc, site.Url);
                            }
                        }

                        break;
                    case MigScope.WebApp:

                        foreach (SPWebApplication webApp in SPWebService.ContentService.WebApplications)
                        {
                            string webUrl = webApp.GetResponseUri(SPUrlZone.Default).AbsoluteUri;
                            if (webUrl == spUrl)
                            {
                                foreach (SPSite site in webApp.Sites)
                                {
                                    GetSiteKeywords(ref xmlDoc, site.Url);
                                }
                                break;
                            }
                        }
                        break;

                    case MigScope.SiteColl:
                        GetSiteKeywords(ref xmlDoc, spUrl);
                        break;
                }

                xmlDoc.Save(outputPath);
            }
			catch (Exception ex)
			{
				throw new InvalidOperationException("Error retrieving url '" + spUrl + "'.  Please check the format of your url, and ensure that the site exists.  Details: " + ex.Message);
			}
        }

        /// <summary>
        /// Get Site Collection Keywords, Best Bets and Synonyms (into the XmlDocument)
        /// </summary>
        /// <param name="xmlDoc">Reference to instanciated XmlDocument for writing</param>
        /// <param name="spUrl">Current Site Collection Url to Export</param>
        private static void GetSiteKeywords(ref XmlDocument xmlDocRef, string spUrl) {
            try {
                XmlDocument xmlDoc = xmlDocRef;

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    using (SPSite spsite = new SPSite(spUrl))
				    {
                        XmlNode docElement = xmlDoc.DocumentElement;
                        XmlElement xmlSite = xmlDoc.CreateElement("Site");
                        docElement.AppendChild(xmlSite);

                        SearchServiceApplicationProxy proxy = (SearchServiceApplicationProxy)SearchServiceApplicationProxy.GetProxy(SPServiceContext.GetContext(spsite));
                        Keywords keywords = new Keywords(proxy, new Uri(spsite.Url));

                        CreateElement("Url", spsite.Url, xmlSite, xmlDoc);
                        XmlElement xmlKeywords = xmlDoc.CreateElement("Keywords");
                        xmlSite.AppendChild(xmlKeywords);

                        foreach (Keyword keyword in keywords.AllKeywords)
                        {

                            XmlElement xmlKeyword = xmlDoc.CreateElement("Keyword");
                            xmlKeywords.AppendChild(xmlKeyword);

						    CreateElement("Term", keyword.Term, xmlKeyword, xmlDoc);
						    XmlElement xmlSynonyms = xmlDoc.CreateElement("Synonyms");
						    xmlKeyword.AppendChild(xmlSynonyms);
						    foreach (Synonym synonym in keyword.Synonyms)
						    {
							    CreateElement("Synonym", synonym.Term, xmlSynonyms, xmlDoc);
						    }
						    CreateElement("Definition", keyword.Definition, xmlKeyword, xmlDoc);
						    CreateElement("Contact", keyword.Contact, xmlKeyword, xmlDoc);
						    if (keyword.StartDate.Date != DateTime.MaxValue.Date)
						    {
							    CreateElement("StartDate", keyword.StartDate.ToShortDateString(), xmlKeyword, xmlDoc);
						    }
						    else
						    {
							    CreateElement("StartDate", "", xmlKeyword, xmlDoc);
						    }
						    if (keyword.EndDate.Date != DateTime.MaxValue.Date)
						    {
							    CreateElement("EndDate", keyword.EndDate.ToShortDateString(), xmlKeyword, xmlDoc);
						    }
						    else
						    {
							    CreateElement("EndDate", "", xmlKeyword, xmlDoc);
						    }
						    if (keyword.ReviewDate.Date != DateTime.MaxValue.Date)
						    {
							    CreateElement("ReviewDate", keyword.ReviewDate.ToShortDateString(), xmlKeyword, xmlDoc);
						    }
						    else
						    {
							    CreateElement("ReviewDate", "", xmlKeyword, xmlDoc);
						    }
						    XmlElement xmlBestBets = xmlDoc.CreateElement("BestBets");
						    xmlKeyword.AppendChild(xmlBestBets);

                            foreach (BestBet bestBet in keyword.BestBets)
						    {
							    XmlElement xmlBestBet = xmlDoc.CreateElement("BestBet");
                                xmlBestBets.AppendChild(xmlBestBet);
                                CreateElement("Title", bestBet.Title, xmlBestBet, xmlDoc);
                                CreateElement("Url", bestBet.Url.ToString(), xmlBestBet, xmlDoc);
                                CreateElement("Description", bestBet.Description, xmlBestBet, xmlDoc);
						    }
					    }
				    }
                });
            }
			catch (Exception ex)
			{
				throw new InvalidOperationException("Error retrieving url '" + spUrl + "'.  Please check the format of your url, and ensure that the site exists.  Details: " + ex.Message);
			}
        }

        /// <summary>
        /// Import the Keywords, Best Bets and Synonyms in the Sites Listed in the migration file
        /// </summary>
        /// <param name="inputPath">Full path to the migration file</param>
        /// <param name="bCleanUp">If true, clear the old Keywords, Best Bets and Synonyms before adding new ones</param>
        /// <returns></returns>
        public static bool ImportKeywords(string inputPath, bool bCleanUp)
        {
            const string siteXPath = "/Sites/Site";
            const string keywordXPath = "Keywords/Keyword";
            const string urlXPath = "Url";

            bool result = false;
            XPathDocument xPathDocument;
            try
            {
                xPathDocument = new XPathDocument(inputPath);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Error loading '" + inputPath + "'.  Please check the format of your file.  Details: " + ex.Message);
            }

            XPathNavigator xPathNavigator = xPathDocument.CreateNavigator();
            XPathNodeIterator siteIterator = xPathNavigator.Select(siteXPath);
            int siteCount = siteIterator.Count;

            //Process each Site Node
            while (siteIterator.MoveNext())
            {
                string siteUrl = string.Empty;
                XPathNavigator nav = siteIterator.Current;
                XPathNavigator urlNode = nav.SelectSingleNode(urlXPath);
                if (!string.IsNullOrEmpty(urlNode.Value))
                {
                    siteUrl = urlNode.Value;
                }

                if (!string.IsNullOrEmpty(siteUrl))
                {
                    List<SearchKeyword> siteKeywords = new List<SearchKeyword>();

                    #region Get Keywords, Synonyms and Best Bets
                    XPathNodeIterator kwIterator = nav.Select(keywordXPath);
                    while (kwIterator.MoveNext())
                    {
                        XPathNavigator kw = kwIterator.Current;
                        siteKeywords.Add(new SearchKeyword(kw));
                    }
                    #endregion

                    try
                    {
                        if (siteKeywords.Count <= 0) continue;
                        SPSecurity.RunWithElevatedPrivileges(delegate()
                        {

                            using (SPSite spsite = new SPSite(siteUrl))
                            {

                                SearchServiceApplicationProxy proxy = (SearchServiceApplicationProxy)SearchServiceApplicationProxy.GetProxy(SPServiceContext.GetContext(spsite));
                                Keywords keywords = new Keywords(proxy, new Uri(spsite.Url));

                                #region Clean Up Keywords in Site Collection
                                if (bCleanUp)
                                {
                                    List<string> allterms = new List<string>();
                                    int ttl = keywords.AllKeywords.Count;
                                    foreach (Keyword keyw in keywords.AllKeywords)
                                    {
                                        allterms.Add(keyw.Term);
                                    }
                                    foreach (string word in allterms)
                                    {
                                        keywords.AllKeywords[word].Delete();
                                    }
                                }
                                #endregion

                                #region Add Keywords to Site Collection
                                foreach (SearchKeyword kw in siteKeywords)
                                {
                                    Keyword KWord = keywords.AllKeywords.Create(kw.Term, kw.StartDate);
                                    KWord.Contact = kw.Contact;
                                    KWord.Definition = kw.Definition;

                                    //End date
                                    if (!string.IsNullOrEmpty(kw.EndDate))
                                    {
                                        DateTime dt;
                                        if (DateTime.TryParse(kw.EndDate, out dt))
                                        {
                                            KWord.EndDate = dt;
                                        }
                                    }

                                    //Review Date
                                    if (!string.IsNullOrEmpty(kw.ReviewDate))
                                    {
                                        DateTime dt;
                                        if (DateTime.TryParse(kw.ReviewDate, out dt))
                                        {
                                            KWord.ReviewDate = dt;
                                        }
                                    }

                                    //Synonyms
                                    foreach (string sym in kw.Synonyms)
                                    {
                                        KWord.Synonyms.Create(sym);
                                    }

                                    //BestBets
                                    foreach (SearchBestBets bb in kw.BestBets)
                                    {
                                        KWord.BestBets.Create(bb.Title, bb.Description, new Uri(bb.URL));
                                    }

                                    //Update keyword with best bets
                                    KWord.Update();
                                }
                                #endregion
                            }
                        });

                    }
                    catch (Exception ex)
                    {
                        //Skip when the Term already exists
                        if (!ex.Message.Contains("is already used as a Keyword Phrase or Synonym."))
                        {
                            string msg = string.Format("An error occurred at '{0}'. Details: {1}", siteUrl, ex.Message);
                            throw new InvalidOperationException(msg);
                        }
                    }
                }
                result = true;
            }

            return result;
        }

    }
}

